//! Preset color filters.

extern crate image;
use crate::channels::{alter_blue_channel, alter_two_channels};
use crate::PhotonImage;
use crate::{effects, helpers, monochrome};
use image::{GenericImage, GenericImageView};
use wasm_bindgen::prelude::*;

/// Solarization on the Blue channel.
///
/// # Arguments
/// * `img` - A PhotonImage.
/// # Example
///
/// ```
/// photon::filters::neue(&mut img);
/// ```
#[wasm_bindgen]
pub fn neue(mut photon_image: &mut PhotonImage) {
    let mut img = helpers::dyn_image_from_raw(&photon_image);
    let (width, height) = img.dimensions();

    for x in 0..width {
        for y in 0..height {
            let mut px = img.get_pixel(x, y);
            if 255 as i32 - px.data[2] as i32 > 0 {
                px.data[2] = 255 - px.data[2];
            }
            img.put_pixel(x, y, px);
        }
    }
    photon_image.raw_pixels = img.raw_pixels();
}

#[wasm_bindgen]
pub fn gamma_transfer(photon_image: &mut PhotonImage, gamma: f32) {
    let length = photon_image.width * photon_image.height;

    let mut lookup_table = vec![0; 256];
    for i in 0..256 {
        let calculated_value = 255.0 * (i as f32 / 255.0).powf(gamma);
        lookup_table[i] = num::clamp(calculated_value, 0.0, 255.0) as u8;
    }

    let mut index = 0;
    for _i in 0..length {
        let looked_value = lookup_table[photon_image.raw_pixels[index] as usize];
        photon_image.raw_pixels[index] = looked_value;
        photon_image.raw_pixels[index + 1] = looked_value;
        photon_image.raw_pixels[index + 2] = looked_value;
        index += 4;
    }
}

#[wasm_bindgen]
pub fn double_gamma(photon_image: &mut PhotonImage, gamma: f32) {
    let length = photon_image.width * photon_image.height;

    let mut lookup_table = vec![0; 256];
    for i in 0..256 {
        let mut calculated_value = i as f32;
        let curr_ratio_value = i as f32 / 255.0;
        if curr_ratio_value > 0.5 {
            calculated_value = (((2.0 * (curr_ratio_value - 0.5)).powf(gamma) + 1.0) / 2.0) * 255.0;
        } else if curr_ratio_value < 0.5 {
            calculated_value = ((1.0 - (2.0 * (0.5 - curr_ratio_value)).powf(gamma)) / 2.0) * 255.0;
        }
        lookup_table[i] = num::clamp(calculated_value, 0.0, 255.0) as u8;
    }

    let mut index = 0;
    for _i in 0..length {
        let looked_value = lookup_table[photon_image.raw_pixels[index] as usize];
        photon_image.raw_pixels[index] = looked_value;
        photon_image.raw_pixels[index + 1] = looked_value;
        photon_image.raw_pixels[index + 2] = looked_value;
        index += 4;
    }
}

#[inline]
fn hist_cal(block: &mut Vec<u8>, length: usize) -> Vec<u32> {
    let mut histogram = vec![0; 256];
    for i in 0..length {
        let pixel_value = block[i];
        histogram[pixel_value as usize] += 1;
    }
    return histogram;
}

#[inline]
fn equalize_histogram(
    pixcount: u32,
    histogram: &mut Vec<u32>,
    limit: u32,
    lut: &mut Vec<f32>,
    offset: usize,
) {
    let mut steal = 0;
    for k in 0..256 {
        if histogram[k] > limit {
            steal += histogram[k] - limit;
            histogram[k] = limit;
        }
    }
    let bonus = steal >> 8;
    let res = steal - (bonus << 8);
    if res > 0 {
        let res_inv = 256 / res;
        let res_step = if res_inv > 1 { res_inv as usize } else { 1 };
        for k in 0..(res as usize) {
            histogram[k * res_step] += 1;
        }
    }
    let mut pro_den = vec![0.0; 256];
    let mut sum = 0.0;
    for k in 0..256 {
        histogram[k] += bonus;
        pro_den[k] = histogram[k] as f32 / pixcount as f32;
        sum += pro_den[k];
        lut[offset + k] = sum * 255.0;
    }
}

#[wasm_bindgen]
pub fn clahe(photon_image: &mut PhotonImage, block_number: u32, limit: u32) {
    let height = photon_image.height;
    let width = photon_image.width;
    let block_height = height / block_number;
    let block_width = width / block_number;
    // 初始化累积分布函数
    let mut all_lut = vec![0.0; ((block_number * block_number) << 8) as usize];
    let block_size = block_width * block_height;
    let row_incr = (block_height * width) << 2;
    let col_incr = block_width << 2;
    let mut row_start_index = 0;
    // 求得各个块的累积分布函数
    for i in 0..block_number {
        let mut block_start_index = row_start_index;
        for j in 0..block_number {
            let mut block = vec![0; (block_width * block_height) as usize];
            let hist_size = ((i * block_number + j) << 8) as usize;
            for k in 0..(block_height as usize) {
                let mut start_index = block_start_index + ((k as u32 * width) << 2);
                for l in 0..(block_width as usize) {
                    block[(block_width * k as u32 + l as u32) as usize] =
                        photon_image.raw_pixels[start_index as usize];
                    start_index += 4;
                }
            }
            let mut hist_block = hist_cal(&mut block, (block_size) as usize);
            equalize_histogram(block_size, &mut hist_block, limit, &mut all_lut, hist_size);
            block_start_index += col_incr;
        }
        row_start_index += row_incr;
    }

    // 插值
    let half_width = block_width >> 1;
    let half_height = block_height >> 1;
    for m in 0..height {
        for n in 0..width {
            // four coners
            // 在角上的像素点为累积分布函数得到的值
            let index = ((m * width + n) as usize) << 2;
            let new_value;
            let current_value = photon_image.raw_pixels[index] as u32;
            if m <= half_height && n <= half_width {
                let block_m = 0;
                let block_n = 0;
                new_value = (all_lut
                    [(((block_m * block_number + block_n) << 8) + current_value) as usize])
                    as u8;
            } else if m <= half_height && (n >= width - half_width) {
                let block_m = 0;
                let block_n = block_number - 1;
                new_value = (all_lut
                    [(((block_m * block_number + block_n) << 8) + current_value) as usize])
                    as u8;
            } else if n <= half_width && m >= height - half_height {
                let block_m = block_number - 1;
                let block_n = 0;
                new_value = (all_lut
                    [(((block_m * block_number + block_n) << 8) + current_value) as usize])
                    as u8;
            } else if m >= height - half_height && n >= width - half_width {
                let block_m = block_number - 1;
                let block_n = block_number - 1;
                new_value = (all_lut
                    [(((block_m * block_number + block_n) << 8) + current_value) as usize])
                    as u8;
            }
            // four edges except coners
            // 四个边采取线性插值
            else if n <= half_width {
                let block_m1 = num::clamp((m - half_height) / block_height, 0, 6);
                let block_n1 = 0;
                let block_m2 = block_m1 + 1;
                let block_n2 = block_n1;
                let u = (m as f32 - ((block_m1 * block_height) + half_height) as f32)
                    / block_height as f32;
                let v = 1 as f32 - u;
                new_value = (v * all_lut
                    [(((block_m1 * block_number + block_n1) << 8) + current_value as u32) as usize]
                    + u * all_lut[(((block_m2 * block_number + block_n2) << 8)
                        + current_value as u32) as usize]) as u8;
            } else if m <= half_height {
                let block_m1 = 0;
                let block_n1 = num::clamp((n - half_width) / block_width, 0, 6);
                let block_m2 = block_m1;
                let block_n2 = block_n1 + 1;
                let u =
                    (n as f32 - (block_n1 * block_width + half_width) as f32) / block_width as f32;
                let v = 1.0 - u;
                new_value = (v * all_lut
                    [(((block_m1 * block_number + block_n1) << 8) + current_value as u32) as usize]
                    + u * all_lut[(((block_m2 * block_number + block_n2) << 8)
                        + current_value as u32) as usize]) as u8;
            } else if m >= height - half_height {
                let block_m1 = block_number - 1;
                let block_n1 = num::clamp((n - half_width) / block_width, 0, 6);
                let block_m2 = block_m1;
                let block_n2 = block_n1 + 1;
                let u =
                    (n as f32 - (block_n1 * block_width + half_width) as f32) / block_width as f32;
                let v = 1.0 - u;
                new_value = (v * all_lut
                    [(((block_m1 * block_number + block_n1) << 8) + current_value as u32) as usize]
                    + u * all_lut[(((block_m2 * block_number + block_n2) << 8)
                        + current_value as u32) as usize]) as u8;
            } else if n >= width - half_width {
                let block_m1 = num::clamp((m - half_height) / block_height, 0, 6);
                let block_n1 = block_number - 1;
                let block_m2 = block_m1 + 1;
                let block_n2 = block_n1;
                let u = (m as f32 - (block_m1 * block_height + half_height) as f32)
                    / block_height as f32;
                let v = 1.0 - u;
                new_value = (v * all_lut
                    [(((block_m1 * block_number + block_n1) << 8) + current_value as u32) as usize]
                    + u * all_lut[(((block_m2 * block_number + block_n2) << 8)
                        + current_value as u32) as usize]) as u8;
            }
            // 中间区域采取双线性插值
            else {
                let block_m1 = num::clamp((m - half_height) / block_height, 0, 6);
                let block_n1 = num::clamp((n - half_width) / block_width, 0, 6);
                let i1 = (((block_m1 * block_number + block_n1) << 8) + current_value) as usize;
                let i2 = i1 + 256;
                let i3 = i1 + (block_number << 8) as usize;
                let i4 = i3 + 256;
                let u =
                    (m - ((block_m1 * block_height) + (half_height))) as f32 / block_height as f32;
                let v = (n - ((block_n1 * block_width) + (half_width))) as f32 / block_width as f32;
                let uv = u * v;
                new_value = ((1.0 - u - v + uv) as f32 * all_lut[i1]
                    + (v - uv) as f32 * all_lut[i2]
                    + (u - uv) as f32 * all_lut[i3]
                    + uv as f32 * all_lut[i4]) as u8;
            }
            photon_image.raw_pixels[index] = new_value;
            photon_image.raw_pixels[index + 1] = new_value;
            photon_image.raw_pixels[index + 2] = new_value;
        }
    }
}

/// Solarization on the Red and Green channels.
///
/// # Arguments
/// * `img` - A PhotonImage.
/// # Example
///
/// ```
/// photon::filters::lix(&mut img);
/// ```
#[wasm_bindgen]
pub fn lix(mut photon_image: &mut PhotonImage) {
    let mut img = helpers::dyn_image_from_raw(&photon_image);

    let (width, height) = img.dimensions();

    for x in 0..width {
        for y in 0..height {
            let mut px = img.get_pixel(x, y);

            px.data[0] = 255 - px.data[0];
            px.data[1] = 255 - px.data[1];

            img.put_pixel(x, y, px);
        }
    }
    photon_image.raw_pixels = img.raw_pixels();
}

/// Solarization on the Red and Blue channels.
///
/// # Arguments
/// * `img` - A PhotonImage.
/// # Example
///
/// ```
/// photon::filters::ryo(&mut img);
/// ```
#[wasm_bindgen]
pub fn ryo(mut photon_image: &mut PhotonImage) {
    let mut img = helpers::dyn_image_from_raw(&photon_image);

    let (width, height) = img.dimensions();

    for x in 0..width {
        for y in 0..height {
            let mut px = img.get_pixel(x, y);
            if 255 as i32 - px.data[2] as i32 > 0 {
                px.data[0] = 255 - px.data[0];
                px.data[2] = 255 - px.data[2];
            }
            img.put_pixel(x, y, px);
        }
    }
    photon_image.raw_pixels = img.raw_pixels();
}

/// Apply a filter to an image. Over 20 filters are available.
/// The filters are as follows:
/// * **oceanic**: Add an aquamarine-tinted hue to an image.
/// * **islands**: Aquamarine tint.
/// * **marine**: Add a green/blue mixed hue to an image.
/// * **seagreen**: Dark green hue, with tones of blue.
/// * **flagblue**: Royal blue tint
/// * **liquid**: Blue-inspired tint.
/// * **diamante**: Custom filter with a blue/turquoise tint.
/// * **radio**: Fallout-style radio effect.
/// * **twenties**: Slight-blue tinted historical effect.
/// * **rosetint**: Rose-tinted filter.
/// * **mauve**: Purple-infused filter.
/// * **bluechrome**: Blue monochrome effect.
/// * **vintage**: Vintage filter with a red tint.
/// * **perfume**: Increase the blue channel, with moderate increases in the Red and Green channels.
/// * **serenity**: Custom filter with an increase in the Blue channel's values.
/// # Arguments
/// * `img` - A PhotonImage.
/// * `blend_mode` - The blending mode to use. See above for complete list of blend modes available.
/// # Example
///
/// ```
/// // For example, to add a filter called "vintage" to an image:
/// use photon::filters;
/// photon::filters::filter(&mut img, "vintage");
/// ```
#[wasm_bindgen]
pub fn filter(img: &mut PhotonImage, filter_name: &str) {
    match filter_name {
        // Match filter name to its corresponding function.
        "oceanic" => alter_two_channels(img, 1, 9, 2, 173),
        "islands" => alter_two_channels(img, 1, 24, 2, 95),
        "marine" => alter_two_channels(img, 1, 14, 2, 119),
        "seagreen" => alter_two_channels(img, 1, 68, 2, 62),
        "flagblue" => alter_blue_channel(img, 131),
        "diamante" => alter_two_channels(img, 1, 82, 2, 87),
        "liquid" => alter_two_channels(img, 1, 10, 2, 75),
        "radio" => monochrome::monochrome(img, 5, 40, 20),
        "twenties" => monochrome::monochrome(img, 18, 12, 20),
        "rosetint" => monochrome::monochrome(img, 80, 20, 31),
        "mauve" => monochrome::monochrome(img, 90, 40, 80),
        "bluechrome" => monochrome::monochrome(img, 20, 30, 60),
        "vintage" => effects::tint(img, 120, 70, 13),
        "perfume" => effects::tint(img, 80, 40, 120),
        "serenity" => effects::tint(img, 10, 40, 90),
        _ => monochrome::monochrome(img, 90, 40, 80),
    };
}
